package classes;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;

public class AngleRangeDegrees implements Comparable<AngleRangeDegrees>  {
	public float	start=0f;	// Represents the start of the angular range
	public float	diff=360f;	// Represents the angular difference in the values
	
	public static final AngleRangeDegrees	COMPLETE_CIRCLE = new AngleRangeDegrees(0f,360f);
	
	public static final float NormalizeAngleDegrees(float angle) {
		while(angle<0) {
			angle+=360f;
		}
		angle = angle % 360f;
		return angle;
	}
	
	public AngleRangeDegrees() {
		set(0f,360f);
	}
	public AngleRangeDegrees(float angle, float diff) {
		set(angle,diff);
	}
	public AngleRangeDegrees getInverse() {
		return new AngleRangeDegrees(start+diff, 360f-diff);
	}
	
	public float getEnd() {
		return NormalizeAngleDegrees(start+diff);
	}
	public float getMiddle() {
		return NormalizeAngleDegrees(start+(diff/2f));
	}
	public boolean contains(float angle) {
		angle = NormalizeAngleDegrees(angle);
		if (angle<start)	angle+=360f;
		return (angle>=start && angle < start+diff);
	}
	public void	set(float start, float diff) {
		//
		// We do not care about angle ranges > 360 degrees!
		// diff must be positive and the start must also be positive
		//
		if (diff<0f) {
			set(start + diff, -diff);
		} else {
			start = NormalizeAngleDegrees(start);	// ensures angle >= 0f angle < 360f
		
			if (diff>360f) {
				diff = 360f;
			}
			this.start = start;
			this.diff = diff;
		}
	}
	
	public static final ArrayList<AngleRangeDegrees>	getUnion(AngleRangeDegrees A, AngleRangeDegrees B) {
		//
		// This will get the union of two angle sweeps. Note that it is possible to STILL return
		// two angle sets!
		//
		if (A.start > B.start) {
			return getUnion(B,A);
		}
		
		ArrayList<AngleRangeDegrees>	L = new ArrayList<AngleRangeDegrees>();
		
		if (A.start+A.diff >= B.start) {
			L.add( 
				new AngleRangeDegrees(
					A.start, 
					Math.max(
							B.start+B.diff-A.start,
							A.diff
					)
				)
			);
		} else {
			if (B.start + B.diff - 360f >= A.start) {
				L.add( 
					new AngleRangeDegrees(
						B.start, 
						Math.max(
								A.start + A.diff - (B.start-360f),
								B.diff
						)
					)
				);
			} else {
				L.add(A);
				L.add(B);
			}
		}
		return L;
	}
	public static final ArrayList<AngleRangeDegrees>	getIntersections(AngleRangeDegrees A, AngleRangeDegrees B) {
		
		if (A.start > B.start) {
			return getIntersections(B,A);
		}

		ArrayList<AngleRangeDegrees>	L = new ArrayList<AngleRangeDegrees>();
		
		// First check to see if the A case covers the entire angle sweep
		if (A.diff >= 360f) {
			if (B.diff < 360f) {
				L.add(new AngleRangeDegrees(B.start+B.diff , 360f-B.diff));
			} else {
				// In this case the intersection is the entire range!
				L.add(new AngleRangeDegrees(0,360f));
			}
		} else if (B.diff >= 360f) { 
			if (A.diff < 360f) {
				L.add(new AngleRangeDegrees(A.start+A.diff , 360f-A.diff));
			} else {
				// We should never reach this case because it's covered above.
				// It's the case where we would have total coverage. This code is unreachable
			}
		} else {
			//
			// Note it is impossible for a sweep to wrap multiple times due to how we defined
			// out AngleRangeDegrees to have a max diff value == 360f once set
			//
			
			// Check if Asweep passes over the B.start
			if (A.start + A.diff > B.start) {
				L.add( new AngleRangeDegrees( B.start, Math.min(A.start+A.diff , B.start+B.diff) - B.start) );
			}

			// Check if the B.sweep passes over the A.start
			if (B.start + B.diff > A.start + 360f) {
				L.add( new AngleRangeDegrees( A.start+360f , Math.min(B.start + B.diff, A.start + 360f + A.diff) - (A.start+360f)) );
			}
		}
		
		return L;
	}
	public static final ArrayList<AngleRangeDegrees>	getDiff(AngleRangeDegrees A, AngleRangeDegrees B) {
		//
		// This will return an array list of float ranges that represent the difference between
		// two FloatRanges. A-B (Not the same as B-A!)
		//
		ArrayList<AngleRangeDegrees>	results = new ArrayList<AngleRangeDegrees>();
		
		if (B.diff >= 360f) {
			//
			// Add nothing. There will be nothing remaining!!! :)
			//
		} else if (A.diff >= 360f) {
			//
			// We add everything that is not in B. The inverse
			//
			results.add(B.getInverse());
		} else {
			//
			// This is more complicated. We will need all the possible intersection regions
			//
			
			ArrayList<AngleRangeDegrees>	Ilist	= getIntersections(A,B);
			if (Ilist.size()<1) {
				results.add(A);
			} else if (Ilist.size()==1) {
				//
				// In this case the intersection cuts off a portion of A
				// either at the front or at the end, but not both!
				//
				// This should give us
				//  0                                             360
				//  |                                              |
				//  |   [-----------------C--------------------]   |
				//	|	[-----------------A----------]             |
				//  |            [--------B--------------------]   |
				//	|            [------- I ---------]             |
				//  |   [--A-B--]                                  |
				//  |   [--C-B--]                                  |
				//  |                                 [--B-A---]   |
				//  |                                 [--C-B---]   |
				//	|   [--C-I--]                     [--C-I---]   |
				//  |                                              |
				//  			
				AngleRangeDegrees	I = Ilist.get(0);
				float				Id = I.diff;
				float				Is = I.start;
				float				Ie = Is + Id;
				float				Ad = A.diff;
				float				As = A.start;
				float				Ae = As + Ad;
				
				// Add the portion before the intersection if it exists
				// Remember, we want ALL of A. :)
				
				if (Ie < Ae) {
					results.add( new AngleRangeDegrees(Ie, Ae - (Is+Id) ));
				}

				// Add the portion after the intersection if it exists
				// Remember, we want ALL of A. :)
				if (A.start < I.start) {
					results.add( new AngleRangeDegrees(As, Is-As ));
				}
				
			} else if (Ilist.size()==2) {
				//
				// This should give us
				//  0                                             360
				//  |                                              |
				//	|	[---------------- A -------------------]   |
				//	|----B---]                         [- B -------|
				//	|	[-I0-]                         [---I1--]   |
				//  |         [-------- A-B ----------]            |
				//  |                                              |
				//
				AngleRangeDegrees	I0 = Ilist.get(0);
				AngleRangeDegrees	I1 = Ilist.get(1);
				AngleRangeDegrees	tmp = null;
				float				I0_diff = I0.diff;
				float				I0_start = I0.start;
				float				I0_end = I0_start + I0_diff;
				float				I1_diff = I1.diff;
				float				I1_start = I1.start;
				float				I1_end = I1_start + I1_diff;
				float				ItotalDiff = I0_diff + I1_diff;
				float				A_diff = A.diff;

				// Make sure we know which intersection covers what end
				// One intersection should start where A starts, the other ends
				// where A ends
				if (I0_end < I1_start) {
					tmp = new AngleRangeDegrees(I0_end, A_diff - ItotalDiff);
				} else {
					tmp = new AngleRangeDegrees(I1_end, A_diff - ItotalDiff);
				}
				results.add(tmp);
			} else {
				printAngleRanges("Error in getDiff() - How did we get " + Integer.toString(Ilist.size()) + " intersections?", Ilist);
			}
		}
		return results;
	}
	
	public String toString() {
		return "( " + Double.toString(start) + " , " + Double.toString(diff) + " )";
	}
	
	public boolean equals(AngleRangeDegrees A) {
		return	(A.diff>=360f && diff >= 360f) ||
				(A.start == start && A.diff==diff);
		
	}

	public static final int compareRanges(AngleRangeDegrees A, AngleRangeDegrees B) {
		if (A==null && B==null) return 0;
		if (A==null) return -1;
		if (B==null) return 1;
		if (A.start < B.start) return -1;
		if (A.start > B.start) return 1;
		if (A.diff < B.diff) return -1;
		if (A.diff > B.diff) return 1;
		return 0;
	}
	@Override
	public int compareTo(AngleRangeDegrees o) {
		return compareRanges(this,o);
	}
	public static final String	stringAngleRanges(String titleMessage, Collection<AngleRangeDegrees> angles) {
		Iterator<AngleRangeDegrees>	itr	= angles.iterator();
		String output = "";
		
		output += titleMessage + "\n";
		
		while(itr.hasNext()) {
			output += "   " + itr.next().toString() + "\n";
		}
		return output;
	}
	public static final void	printAngleRanges(String titleMessage, Collection<AngleRangeDegrees> angles) {
		System.out.println(stringAngleRanges(titleMessage,angles));
	}
	public static final LinkedList<AngleRangeDegrees>	InitialAngleRangeCoverage(AngleRangeDegrees F) {
		LinkedList<AngleRangeDegrees>	list = new LinkedList<AngleRangeDegrees>();
		list.add(F);
		return list;
	}
	public static final	LinkedList<AngleRangeDegrees>	TotalCircleCoverage() {
		LinkedList<AngleRangeDegrees>	circleCoverage = new LinkedList<AngleRangeDegrees>();
		circleCoverage.add(new AngleRangeDegrees(0f,360f));
		return circleCoverage;
	}
	public static final LinkedList<AngleRangeDegrees>	FillAngleHoles(LinkedList<AngleRangeDegrees> currentHoles, AngleRangeDegrees fillRange) {
		//
		// This will iterate over all the holes currently detected and check for the possible differences with fill
		// range and return a new hole list
		//
		Iterator<AngleRangeDegrees>	itr = currentHoles.iterator();
		
		AngleRangeDegrees				current = null;
		LinkedList<AngleRangeDegrees>	newHoles = new LinkedList<AngleRangeDegrees>();
		ArrayList<AngleRangeDegrees>	diff = null;
		
		while(itr.hasNext()) {
			current			= itr.next();
			diff 			= AngleRangeDegrees.getDiff(current, fillRange);
			newHoles.addAll(diff);
		}
		return newHoles;
	}
	public static final LinkedList<AngleRangeDegrees>	FillAllAngleHoles(LinkedList<AngleRangeDegrees> initialHoles, LinkedList<AngleRangeDegrees> allFills) {
		//
		// This basically takes one fill at a time and constantly plugs holes in starting with initialHoles until
		// all the fillRanges have been exhausted
		//
		Iterator<AngleRangeDegrees>		itr = allFills.iterator();
		LinkedList<AngleRangeDegrees>	currentHoles = initialHoles;
		AngleRangeDegrees				fill = null;
		AngleRangeDegrees				first = null;
		
		while(itr.hasNext()) {
			fill = itr.next();
			if (first==null)	first = fill;
			currentHoles = FillAngleHoles(currentHoles, fill);
		}
		
		if (fill!=null) {
			currentHoles = FillAngleHoles(currentHoles, first);
		}
		
		// This is needed because we are actually taking the difference with the next
		// element in each hole. We then need to check the remainder
		
		return currentHoles;
	}


}
